package sicnu.cs.ich.token.security.config;

import lombok.Getter;
import lombok.Setter;
import org.springframework.util.AntPathMatcher;

import java.util.*;
import java.util.function.Consumer;

/**
 * @author CaiKe
 * @date 2021/12/25 15:45
 */
public class TokenSecurity {

    @Getter
    @Setter
    private Object principle = "anonymous";
    /**
     * 用户权限
     */
    @Getter
    @Setter
    private Set<String> authorities;
    /**
     * 匹配器
     */
    private final AntPathMatcher MATCHER;
    /**
     * 维护一个鉴权链
     */
    private final List<AuthPair> AUTH_PAIRS;
    /**
     * 存放“permitAll”类型的路径
     */
    private final List<String> AUTH_URIS;
    private final List<String> DENY_URIS;
    /**
     * 是否可信
     *
     * @param MATCHER
     */
    private boolean credible = false;

    @Setter
    @Getter
    private boolean triedParseToken = false;

    public TokenSecurity(AntPathMatcher MATCHER) {
        this.DENY_URIS = new ArrayList<>();
        this.AUTH_URIS = new ArrayList<>();
        this.authorities = new HashSet<>();
        this.AUTH_PAIRS = new ArrayList<>();
        this.MATCHER = MATCHER;
    }

    /**
     * 标记当前为可信对象（成功解析Token）
     */
    public void assignCredible() {
        credible = true;
    }

    public boolean isCredible() {
        return credible;
    }

    public boolean preCheckAuthURI(String path) {
        // check permitAll
        return matchesAny(AUTH_URIS, path);
    }

    public boolean preCheckDenyURI(String path) {
        // check permitAll
        return matchesAny(DENY_URIS, path);
    }

    /**
     * 检查当前具有的权限是否满足
     *
     * @param path 路径
     */
    public <T> boolean checkAuth(String path) {
        // check Token
        for (AuthPair authPair : AUTH_PAIRS) {
            for (String pattern : authPair.patterns) {
                if (MATCHER.matchStart(pattern, path))
                    return makeDecision(authPair, path);
            }
        }
        return false;
    }

    /**
     * 决策是否放行
     *
     * @param authPair
     * @param path
     * @return
     */
    private boolean makeDecision(AuthPair authPair, String path) {
        final String[] auths = authPair.requiredAuth;
        // TODO 需要考虑使用设计模式吗？会不会显得很臃肿？
        switch (authPair.authMode) {
            case 1:
                //hasAny
                for (String auth : auths)
                    if (authorities.contains(auth)) return true;
                return false;
            case 2:
                //hasAll
                return authorities.containsAll(Arrays.asList(auths));
//            case 3:
//                //permitAll
//                return true;
//            case 4:
//                //denyAll
//                return false;
            case 5:
                // requireCredential
                return credible;
            default:
                return false;
        }
    }

    /**
     * 初始化当前Token中的一个节点
     *
     * @param patterns
     * @return
     */
    public AuthPair antMatchers(String... patterns) {
        return new AuthPair(this, patterns);
    }

    public AuthPair anyRequest() {
        return new AuthPair(this, "/**");
    }

    private void addAuthPair(AuthPair authPair) {
        AUTH_PAIRS.add(authPair);
    }

    private void addAuthUris(String... uris) {
        AUTH_URIS.addAll(Arrays.asList(uris));
    }

    private void addDenyUris(String... uris) {
        DENY_URIS.addAll(Arrays.asList(uris));
    }

    private boolean matchesAny(List<String> patterns, String path) {
        for (String pattern : patterns) {
            if (MATCHER.matchStart(pattern, path)) return true;
        }
        return false;
    }

    ;

//    /**
//     * 添加权限链
//     *
//     * @param authPair
//     */
//    private void addAuthPair(AuthPair authPair) {
//        for (String pattern : authPair.getPatterns()) {
//            String prefix = getPathPrefix(pattern);
//            if (URIS.containsKey(prefix)) {
//                URIS.get(prefix).add(authPair);
//            } else {
//                final ArrayList<AuthPair> objects = new ArrayList<>();
//                objects.add(authPair);
//                URIS.put(prefix, objects);
//            }
//        }
//    }

    private String getPathPrefix(String path) {
        if (path.length() == 0) throw new IllegalArgumentException("Path Cannot be Empty!");
        int index = 0;
        if (path.charAt(0) == '/') {
            index = 1;
        }
        int i = index;
        for (; i < path.length(); i++) {
            if (path.charAt(i) == '/') break;
        }
        return path.substring(index, i);
    }

    public static class AuthPair {
        String[] patterns;
        String[] requiredAuth;
        /**
         * 校验类型  1-HasAny 2-HasAll 3-PermitAll 4-DenyAll 5-RequireCredential
         */
        int authMode;
        TokenSecurity parent;

        public AuthPair(TokenSecurity tokenSecurity, String... patterns) {
            this.patterns = patterns;
            this.parent = tokenSecurity;
        }

        public TokenSecurity hasAny(String... auths) {
            this.requiredAuth = auths;
            return addAuthPair(1);
        }

        public TokenSecurity hasAll(String... auths) {
            this.requiredAuth = auths;
            return addAuthPair(2);
        }

        public TokenSecurity permitAll() {
//            return addAuthPair(3);
            parent.addAuthUris(this.patterns);
            return parent;
        }

        public TokenSecurity denyAll() {
            parent.addDenyUris(this.patterns);
            return parent;
        }

        public TokenSecurity authenticated() {
            return addAuthPair(5);
        }

        private TokenSecurity addAuthPair(Integer authMode) {
            this.authMode = authMode;
            this.parent.addAuthPair(this);
            return parent;
        }
    }


}